﻿using NAudio.CoreAudioApi;
using NAudio.Wave;
using SoundEngine3;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DataTypes;
using SoundEngine3.DspPlugins.Meta;
using System;
using System.Collections.Generic;
using System.Reflection;
using Zquence_Studio_3_WindowsDesktop.SoundEngine.Classes;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine
{
    /// <summary>
    /// Reference: https://cscore.codeplex.com/SourceControl/latest#CSCore/SoundOut/DirectSoundOut.cs
    /// </summary>
    public class WasapiAdapter : WasapiDirectSoundShared
    {
        private SoundEngine3.SoundDevice mCurrentSoundDevice;
        private WasapiOut mDirectSound;
        private MMDevice mDevice;
        private AudioClient mClient; // To get buffer size info.
        private double mSampleRate = 44100; // Default.
        private int mBufferSize;
        private int mOutputChannels;
        private int mLastSampleRate;

        private Dictionary<WasapiCapture, int> mCurrentAudioCapturings = new Dictionary<WasapiCapture, int>();
        private Dictionary<AudioInput, WasapiCapture> mCurrentAudioCapturings2 = new Dictionary<AudioInput, WasapiCapture>();
        private List<WasapiCapture> mCapturings = new List<WasapiCapture>();
        
        private EnumParameter<MillisecondsLatency> bufferSizeProperty;
        private EnumParameter<SampleRates.Value> sampleRateProperty;
        private int Latency;
        private int mSR;

        public override int MinimumBufferSize
        {
            get { return mBufferSize; }
        }

        public override int MaximumBufferSize
        {
            get { return mBufferSize; }
        }

        public override int PreferredBufferSize
        {
            get { return mBufferSize; }
        }

        public override double SampleRate
        {
            get { return mSampleRate; }
        }

        public override int InputChannels { get { return AudioInputs.Count; } }

        public override int OutputChannels
        {
            get { return mOutputChannels; }
        }

        public override bool Started
        {
            get
            {
                return mDirectSound.PlaybackState == PlaybackState.Playing;
            }
        }

        public WasapiAdapter()
            : base("Wasapi (NAudio)")
        {
            // Get all direct sound devices.
            // Reference: https://cscore.codeplex.com/wikipage?title=Choose%20a%20playback%20device&referringTitle=Documentation
            MMDeviceEnumerator enumerator = new MMDeviceEnumerator();
            MMDeviceCollection mmdevices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
            foreach (var mmdevice in mmdevices)
            {
                SoundDevice device = new SoundDevice(mmdevice.FriendlyName, this, mmdevice);
                AddSoundDevice(device);
            }

            MMDeviceCollection mmcaptureDevices = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
            AudioInput.SetChannels(mmcaptureDevices.Count, 1024);
            for (int i = 0; i < mmcaptureDevices.Count; i++)
            {
                string name = mmcaptureDevices[i].FriendlyName;

                // TODO: Det här måste göras om! AudioInput.SetChannels ska konfikueras
                //       automatiskt i AddInput(...) eller något så vi slipper bli tvungen att
                //       sätta namnet på det här korkade sättet!
                AudioInput.Channels[i].Name = name;

                AudioInput input = new AudioInput(this, AudioInput.Channels[i], name);
                AddInput(input);
                
                WasapiCapture audioCapture = new WasapiCapture();
                audioCapture.WaveFormat = new NAudio.Wave.WaveFormat(48000, 16, 1);
                audioCapture.DataAvailable += AudioCapture_DataAvailable;

                mCurrentAudioCapturings.Add(audioCapture, i);
                mCurrentAudioCapturings2.Add(input, audioCapture);
            }
        }

        /// <summary>
        /// Calls when audio capture buffer is available.
        /// </summary>
        private void AudioCapture_DataAvailable(object sender, WaveInEventArgs e)
        {
            WasapiCapture nativeCapture = (WasapiCapture)sender;

            int captureChannelIndex = mCurrentAudioCapturings[nativeCapture];

            Channel CaptureChannel = AudioInput.Channels[captureChannelIndex];

            lock (RecordHelper)
            {
                if (mDirectSound != null && mDirectSound.PlaybackState == PlaybackState.Playing)
                {
                    int bytesPerSample = nativeCapture.WaveFormat.BitsPerSample / 8;
                    for (int i = 0; i < e.BytesRecorded; i += nativeCapture.WaveFormat.BlockAlign)
                    {
                        short value = 0;
                        for (int b = 0; b < bytesPerSample; b++)
                        {
                            byte byteValue = e.Buffer[i + b];

                            short bb = (short)((byteValue & 0xff) << (8 * b));

                            value = (short)(value | bb);
                        }

                        double actualSample = (double)(value / (double)short.MaxValue);
                        RecordHelper.FillInputLockfree(actualSample);
                    }
                }
                else
                {
                    RecordHelper.ClearInput();
                }
            }
        }

        protected override void FeatchInputSamples(Channel inputChannel)
        {
            //lock (this)
            //{
                //// If the captured buffer is larger than featch request. Then take it.
                //if (inputChannel.Samples.Length < mAudioCaptureBuffer.Count)
                //{
                //    double[] buffered = mAudioCaptureBuffer.ToArray();
                //    Array.Copy(buffered, inputChannel.Samples, inputChannel.Samples.Length);
                //    mAudioCaptureBuffer.RemoveRange(0, inputChannel.Samples.Length);
                //}
                RecordHelper.FillOutput(inputChannel.Samples);
            //}
        }

        protected override void OnStartRecording(AudioInput audioInput)
        {
            var nativeAudioInput = mCurrentAudioCapturings2[audioInput];

            RecordHelper.SetupRecording(nativeAudioInput.WaveFormat.SampleRate);

            nativeAudioInput.StartRecording();

            mCapturings.Add(nativeAudioInput);
        }

        protected override void OnStopRecording(AudioInput audioInput)
        {
            var nativeAudioInput = mCurrentAudioCapturings2[audioInput];

            RecordHelper.ClearInput();

            nativeAudioInput.StopRecording();

            mCapturings.Remove(nativeAudioInput);
        }

        public override void Initialize(int bufferSize, int sampleRate)
        {
            // TODO: Now to set latency ms?
            mSampleRate = sampleRate;
        }

        public override void SetSoundDevice(SoundEngine3.SoundDevice soundDevice)
        {
            if (mDirectSound != null)
            {
                Destroy();
            }

            if (soundDevice != null)
            {
                mCurrentSoundDevice = soundDevice;

                int ms = 10;
                
                SetupWasapi(ms, (int)SampleRate);
            }
        }

        protected override void OnDestroy()
        {
            if (mDirectSound != null)
            {
                mDirectSound.Dispose();
                mDirectSound = null;
                mDevice = null;
                mClient = null;
            }
        }

        private void SetupWasapi(int ms, int sampleRate)
        {
            Latency = ms;
            mSR = sampleRate;

            mDevice = mCurrentSoundDevice.GetDeviceIdentifier<MMDevice>();
            mDirectSound = new WasapiOut(mDevice, AudioClientShareMode.Shared, false, ms);

            tryAgain:;
            try
            {
                WaveProvider32 mCurrentBufferedProvider = new WaveProvider32(this, sampleRate, 2);
                mDirectSound.Init(mCurrentBufferedProvider);
            }
            catch (Exception error)
            {
                SharpDepend.Manager.DialogManager.Show(error.Message, "Wasapi error", SharpDepend.DialogIcon.Error);

                sampleRate = mLastSampleRate;
                goto tryAgain;
            }

            mLastSampleRate = mSR;

            // HACK: To get buffer size info.
            var audioClientField = typeof(WasapiOut).GetField("audioClient", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance);
            mClient = (AudioClient)audioClientField.GetValue(mDirectSound);

            // Get current sample rate.
            //mSampleRate = mDevice.AudioClient.MixFormat.SampleRate;
            mSampleRate = sampleRate;
            mBufferSize = mClient.BufferSize;
            mOutputChannels = mClient.MixFormat.Channels;
            RecordHelper.SetupOutput((int)mSampleRate);

            //AudioInput.SetChannels(InputChannels, PreferredBufferSize);
            foreach (var channel in AudioInput.Channels)
            {
                channel.SetBufferSize(PreferredBufferSize);
            }
            RaiseOnInputChannelChanged(InputChannels);

            AudioOutput.SetChannels(OutputChannels, PreferredBufferSize);
            RaiseOnOutputChannelChanged(OutputChannels);

            RaiseOnBufferSizeChanged(PreferredBufferSize, PreferredBufferSize, PreferredBufferSize);

            AudioInput.SampleRate = sampleRate;
            AudioOutput.SampleRate = sampleRate;
            RaiseOnSampleRateChanged(sampleRate);
            
            Config(OutputChannels, PreferredBufferSize);
        }

        public override void Start()
        {
            AudioBufferProcessor.Start();
            mDirectSound.Play();
        }

        public override void Stop()
        {
            AudioBufferProcessor.Stop();
            mDirectSound.Stop();
        }
        
        public override void Dispose()
        {
            Destroy();
            if (mDirectSound != null)
            {
                mDirectSound.Dispose();
            }
        }

        public override void GetGUIControls(List<ParameterProperties> uiControls)
        {
            // Buffer size.
            bufferSizeProperty = new EnumParameter<MillisecondsLatency>(null);
            bufferSizeProperty.EnumValue = (MillisecondsLatency)Latency;
            bufferSizeProperty.EnumMinValue = MillisecondsLatency._20;
            bufferSizeProperty.OnValueChanged += delegate (ParameterProperties value)
            {
                Stop();
                Dispose();
                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    Latency = (int)bufferSizeProperty.EnumValue;
                    SetupWasapi(Latency, mSR);
                }
                Start();
            };
            bufferSizeProperty.Label = "Latency ms";
            uiControls.Add(bufferSizeProperty);

            // Sample rate.
            sampleRateProperty = new EnumParameter<SampleRates.Value>(null);
            sampleRateProperty.EnumValue = (SampleRates.Value)mSR;
            sampleRateProperty.OnValueChanged += delegate (ParameterProperties value)
            {
                Stop();
                Destroy();
                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    mSR = (int)sampleRateProperty.EnumValue;
                    SetupWasapi(Latency, mSR);
                }
                Start();
            };
            sampleRateProperty.Label = "Sample rate";
            uiControls.Add(sampleRateProperty);
        }
    }
}
